#include <boost/test/unit_test.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#include <regex>
#include <array>
#include "parser.h"

BOOST_AUTO_TEST_CASE(ParserTest) {
    std::cout << "Elementary parser tests..." <<  std::endl;
    StringSet set;
    typedef std::pair<std::string,Index> Pair;
#define N 50
    typedef std::array<Pair,N> PairsxN;
    PairsxN Ps = {{
                      Pair("",0) //0
#if 1
                      , Pair("%",1)
                      , Pair("%%",0)
                      , Pair("%\n%%",1)
                      , Pair("%%\n%",1)
                      , Pair("%left",1)
                      , Pair("%left symbol",1)
                      , Pair("%left Symbol",0)
                      , Pair("%left Symbol sym",1)
                      , Pair("%left Symbol Sym",0)
                      , Pair("%left Symbol Sym//comments",0) //10
                      , Pair("%left '*' Sym//comments",1)
                      , Pair("%right",1)
                      , Pair("%right symbol",1)
                      , Pair("%right Symbol",0)
                      , Pair("%right Symbol sym",1)
                      , Pair("%right Symbol Sym",0)
                      , Pair("%right Symbol Sym//comments",0)
                      , Pair("%right '*' Sym//comments",1)
                      , Pair("%nonassoc",1)
                      , Pair("%nonassoc symbol",1) // 20
                      , Pair("%nonassoc Symbol",0)
                      , Pair("%nonassoc Symbol sym",1)
                      , Pair("%nonassoc Symbol Sym",0)
                      , Pair("%nonassoc Symbol Sym//comments",0)
                      , Pair("%nonassoc Symbol\n%nonassoc Symbol\n",1)
                      , Pair("%nonassoc '*' Sym//comments",1)
                      , Pair("%shortcut '*' PRODUIT//comments",0)
                      , Pair("%ifdef",2)
                      , Pair(" % ifdef",2)
                      , Pair("%ifdefx",1)
                      , Pair("%ifdef x",1)
                      , Pair("%ifdef x\n%endif",0)
                      , Pair("%ifdef x\n%ifdef y\n%%\n%endif\n%endif\n",0)
                      , Pair("%ifdef x\n%ifdef y\n%%\n%endif\n\n",1)
                      , Pair("%ifdef x\n%ifdef y\n%%\n%endif\n%endif\n%endif\n",1)
                      , Pair("%ifdef x\n%nonassoc\n%endif\n",0)
                      , Pair("%ifndef x\n%nonassoc\n%endif\n",1)
                      , Pair("%ifdef x\n%else\n%nonassoc\n%endif\n",1)
                      , Pair("%ifndef x\n%else\n%nonassoc\n%endif\n",0)
                      , Pair("%ifdef x\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n",1)
                      , Pair("%ifndef x\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n",0)
                      , Pair("%ifdef x\n"
                      "%ifndef x\n"
                                            "%else\n"
                                            "%nonassoc\n"
                                            "%endif\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n",1)
                      , Pair("%ifndef x\n"
                      "%ifndef x\n"
                                            "%else\n"
                                            "%nonassoc\n"
                                            "%endif\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n",0)
                      , Pair("%ifdef x\n"
                      "%else\n"
                      "%ifdef x\n"
                                            "%else\n"
                                            "%nonassoc\n"
                                            "%endif\n"
                      "%nonassoc\n"
                      "%endif\n",2)
                      , Pair("%ifndef x\n"
                      "%else\n"
                      "%ifdef x\n"
                                            "%else\n"
                                            "%nonassoc\n"
                                            "%endif\n"
                      "%nonassoc\n"
                      "%endif\n",0)
                      , Pair("%define x\n"
                      "%ifdef x\n"
                      "%else\n"
                      "%ifdef y\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n"
                      "%nonassoc\n"
                      "%endif\n",0)
                      , Pair("%define y\n"
                      "%ifdef x\n"
                      "%else\n"
                      "%ifdef y\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n"
                      "%nonassoc\n"
                      "%endif\n",1)
                      , Pair("%define x\n"
                      "%define y\n"
                      "%ifdef x\n"
                      "%else\n"
                      "%ifdef y\n"
                      "%else\n"
                      "%nonassoc\n"
                      "%endif\n"
                      "%nonassoc\n"
                      "%endif\n",0)
                  #endif
                      , Pair("%ifndef x\n"
                      "%define y\n"
                      "%endif\n", 0)

                }};
    Index n = 0;
    for(auto p: Ps) {
        std::string s = p.first;
        Index j = p.second;
        set.clear();
        if (!s.empty()) {
            //std::cout << n++ << ": " << s << "->" << j << std::endl;
            Parser p("?", s, set);
            BOOST_REQUIRE(p.parse()==j);
        }
    }
#define if_tree               "%ifdef x\n"\
    "%%%%%%%%%%\n"\
    "%ifdef y\n"\
    "%%%%%%%%%%\n"\
    "%ifdef z\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%nonassoc\n%nonassoc\n%nonassoc\n%nonassoc\n"\
    "%endif\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%%%%%%%%%%\n"\
    "%nonassoc\n%nonassoc\n"\
    "%ifdef z\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%nonassoc\n%nonassoc\n%nonassoc\n%nonassoc\n"\
    "%endif\n"\
    "%%%%%%%%%%\n"\
    "%endif\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%%%%%%%%%%\n"\
    "%ifdef y\n"\
    "%%%%%%%%%%\n"\
    "%ifdef z\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%nonassoc\n%nonassoc\n%nonassoc\n%nonassoc\n"\
    "%endif\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%%%%%%%%%%\n"\
    "%nonassoc\n%nonassoc\n"\
    "%ifdef z\n"\
    "%%%%%%%%%%\n"\
    "%else\n"\
    "%nonassoc\n%nonassoc\n%nonassoc\n%nonassoc\n"\
    "%endif\n"\
    "%%%%%%%%%%\n"\
    "%endif\n"\
    "%nonassoc\n"\
    "%endif\n"\

    Ps = {{
              Pair("",0) //0
              , Pair("%ifndef x\n"
              "%define y\n"
              "%endif\n"
              "%ifdef y\n"
              "%nonassoc\n"
              "%endif\n",1)
              , Pair("%define x\n"
              "%define y\n"
              "%define z\n"
              if_tree
              ,0)
              , Pair("%define xx\n"
              "%define y\n"
              "%define z\n"
              if_tree
              ,1)
              , Pair("%define x\n"
              "%define yy\n"
              "%define z\n"
              if_tree
              ,2)
              , Pair("%define xx\n"
              "%define yy\n"
              "%define z\n"
              if_tree
              ,3)
              , Pair("%define x\n"
              "%define y\n"
              "%define zz\n"
              if_tree
              ,4)
              , Pair("%define xx\n"
              "%define y\n"
              "%define zz\n"
              if_tree
              ,5)
              , Pair("%define x\n"
              "%define yy\n"
              "%define zz\n"
              if_tree
              ,6)
              , Pair("%define xx\n"
              "%define yy\n"
              "%define zz\n"
              if_tree
              ,7)
              , Pair("%shortcut '?' Xx\n",0)
              , Pair("%shortcut '?' xx\n",1)
              , Pair("%shortcut '?'Xx\n",1)
              , Pair("%shortcut '?' Xx\n%shortcut '?' Yy\n",1)
              , Pair("a::=",1)
              , Pair("a::=",1)
              , Pair("a::=B C\n"
                " code here\n"
              ,0)
              , Pair("%class A ::= B\n"
              ,0)
              , Pair("%class A ::= B / C | D\n"
              ,0)
          }};
    n = 0;
    for(auto p: Ps) {
        std::string s = p.first;
        Index j = p.second;
        set.clear();
        if (!s.empty()) {
            //std::cout << n++ << ": " << s << "->" << j << std::endl;
            Parser p("?", s, set);
            BOOST_REQUIRE(p.parse()==j);
        }
    }
    std::cout << "Done" <<  std::endl;
}

BOOST_AUTO_TEST_CASE(ParserRegexTest) {
    std::cout << "Parser Regex tests..." <<  std::endl;

    std::smatch result;
#define RX_Max 22
    short range[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21 };
    std::array<std::regex,RX_Max> RXs = {{
                                               Parser::comment_rx               /*  0 */
                                               , Parser::ifdef_rx               /*  1 */
                                               , Parser::ifndef_rx              /*  2 */
                                               , Parser::ifdef_or_ifndef_rx     /*  3 */
                                               , Parser::else_rx                /*  5-1 */
                                               , Parser::endif_rx               /*  6-1 */
                                               , Parser::define_rx              /*  7-1 */
                                               , Parser::undef_rx               /*  8-1 */
                                               , Parser::macro_rx               /*  9-1 */
                                               , Parser::shortcut_rx            /* 10-1 */
                                               , Parser::left_rx                /* 11-1 */
                                               , Parser::right_rx               /* 12-1 */
                                               , Parser::nonassoc_rx            /* 13-1 */
                                               , Parser::continuation_rx        /* 14-1 */
                                               , Parser::terminal_symbol_rx     /* 15-1 */
                                               , Parser::non_terminal_symbol_rx /* 16-1 */
                                               , Parser::lhs_symbol_rx          /* 17-1 */
                                               , Parser::rhs_symbol_rx          /* 18-1 */
                                               , Parser::terminal_symbol_or_key_rx  /* 18 */
                                               , Parser::key_rx                 /* 19 */
                                               , Parser::class_rx                 /* 20 */
                                               , Parser::more_terminal_symbol_rx /* 21 */
                                       }};
    std::array<std::string,RX_Max> RXNames = {{
                                               "comment"               /*  0 */
                                               , "ifdef"               /*  1 */
                                               , "ifndef"              /*  2 */
                                               , "ifdef_or_ifndef"     /*  3 */
                                               , "else"                /*  5-1 */
                                               , "endif"               /*  6-1 */
                                               , "define"              /*  7-1 */
                                               , "undef"               /*  8-1 */
                                               , "macro"               /*  9-1 */
                                               , "shortcut"            /* 10-1 */
                                               , "left"                /* 11-1 */
                                               , "right"               /* 12-1 */
                                               , "nonassoc"            /* 13-1 */
                                               , "continuation"        /* 14-1 */
                                               , "terminal_symbol"     /* 15-1 */
                                               , "non_terminal_symbol" /* 16-1 */
                                               , "lhs_symbol"          /* 17-1 */
                                               , "rhs_symbol"              /* 18-1 */
                                               , "terminal_symbol_or_key" /* 18 */
                                                  , "key"                 /* 19 */
                                                  , "class"                 /* 20 */
                                                  , "more_terminal_symbol" /* 21 */
                                       }};
    //BOOST_FOREACH (const size_t i,range);
    typedef std::array<int,RX_Max> Expected;
    typedef std::array<Expected,RX_Max> AllExpected;
    AllExpected matches = {{
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  0 */
                               {{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  1 */
                               {{0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  2 */
                               {{0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  3 */
                               {{0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  4 */
                               {{0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  5 */
                               {{0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  6 */
                               {{0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  7 */
                               {{0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,0,0,0}}, /*  8 */
                               {{0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,0}}, /*  8+1 */
                               {{0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0}}, /*  9+1 */
                               {{0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}}, /*  9+2 */
                               {{0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}}, /* 10+2 */
                               {{0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}}, /* 11+2 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}}, /* 12+2 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0}}, /* 13+2 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0}}, /* 16 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}}, /* 17 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0}}, /* 18 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0}}, /* 19 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}, /* 20 */
                               {{0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1}}  /* 21 */

                  }};
    typedef std::pair<std::string,int> Pair;
#define N 50
    typedef std::array<Pair,N> PairsxN;
    PairsxN P2 = {{
                      Pair("%%",1)
                      , Pair(" %%",1)
                      , Pair(" % %",1)
                      , Pair(" % % ",1)
                      , Pair(" % sdf % XXX",0)
                      , Pair(" % ifdef",2)
                      , Pair(" % ifdef zjnpqnv",2)
                      , Pair(" % ifdefzjnpqnv",0)
                      , Pair(" % ifndef",3)
                      , Pair(" % ifndef zjnpqnv",3)
                      , Pair(" % ifndefzjnpqnv",0)
                      , Pair("%else",4)
                      , Pair(" % else",4)
                      , Pair(" % else peqfjnv",4)
                      , Pair(" % elsepeqfjnv",0)
                      , Pair("%endif",5)
                      , Pair(" % endif",5)
                      , Pair(" % endif peqfjnv",5)
                      , Pair(" % endifpeqfjnv",0)
                      , Pair("%define",6)
                      , Pair("%define ",6)
                      , Pair("%definex",0)
                      , Pair("%define macro c'est élémentaire",6)
                      , Pair("%undef",7)
                      , Pair("%undef ",7)
                      , Pair("%undefx ",0)
                      , Pair("%undef macro c'est élémentaire",7)
                      , Pair("Macro",17)
                      , Pair(" Macro",8)
                      , Pair(" macro",9)
                      , Pair(" _macro",10)
                      , Pair(" 8macro",15)
                      , Pair("%shortcut",11)
                      , Pair(" % shortcut",11)
                      , Pair(" % shortcutx ",0)
                      , Pair("%left",12)
                      , Pair(" % left",12)
                      , Pair(" % left macro",12)
                      , Pair(" % leftmacro",0)
                      , Pair("%right ",13)
                      , Pair(" % right",13)
                      , Pair(" % right macro",13)
                      , Pair(" % rightmacro",0)
                      , Pair("%nonassoc ",14)
                      , Pair(" % nonassoc",14)
                      , Pair(" % nonassoc macro",14)
                      , Pair(" % nonassocmacro",0)
                }};
    for(auto p:P2) {
        std::string s = p.first;
        int j = p.second;
        if (!s.empty()) {
            //std::cout << "Test: " << s << "->" << j << std::endl;
            for(auto i: range) {
                //std::cout << i << "->" << RXNames[i] << "->" << matches[j][i] << std::endl;
                bool yorn = std::regex_search(s.cbegin(), s.cend(), result, RXs[i]);
                BOOST_REQUIRE(matches[j][i]==yorn?1:0);
            }
        }
    }
    P2 = {{
              Pair("macro::=",16)
              , Pair("macro ::=",16)
              , Pair("macro :: =",17)
              , Pair("'c'",0)
              , Pair(" 'c'",18)
              , Pair("%class",19)
              , Pair(" % class",19)
              , Pair(" % class macro",19)
              , Pair(" % classmacro",0)
              , Pair("|Symbol",20)
              , Pair("/Symbol",20)
              , Pair(" | Symbol",21)
              , Pair(" / Symbol",21)
          }};
    for(auto p:P2) {
        std::string s = p.first;
        int j = p.second;
        if (!s.empty()) {
            std::cout << "Test: " << s << "->" << j << std::endl;
            for(auto i: range) {
                std::cout << i << "->" << RXNames[i] << "->" << matches[j][i] << std::endl;
                bool yorn = std::regex_search(s.cbegin(), s.cend(), result, RXs[i]);
                BOOST_REQUIRE(matches[j][i]==yorn?1:0);
            }
        }
    }
}
